﻿using System;
using System.Collections.Generic;
using System.Linq;

using RayDen.Library.Core.Collections;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Entity.Interface;
using RayDen.Library.Entity.Scene;

namespace RayDen.Library.Components {

    public class TriangleMesh : IMesh
    {
        private static int _currentId = 1;

        public const float a = -1f;
        public int Id;
        [NonSerialized]
        public SceneGeometryInfo scene;
        public List<Triangle> triangles;
        public Dictionary<int, Normal[]> normals;
        public Dictionary<int, UV[]> texCoords;
        public bool VolumeMesh;

        public int[] Indexes;
        public int StartVertice;
        public int EndVertice;

        public int TrianglesCount
        {
            get { return this.triangles.Count; }
        }

        
        public bool HasTexCoords
        {
            get { return this.texCoords != null && texCoords.Any(); }
        }
        public string MeshName { get; set; }
        public string MaterialName { get; set; }


        private AABB bounds;
        private bool boundInit;
        public AABB Bounds {
            get {
                if (!boundInit) {
                    //bounds = triangles.Select(item => item.WorldBound(ref scene.Vertices)).Aggregate((b1, b2) => b1.Union(b2));
                    CalcBound();
                    boundInit = true;
                }
                return bounds;
            }
        }

        public TriangleMesh()
        {
            Id = _currentId++;
        }

        public Normal GetTriangleNormal(int triangleIndex, int normalIndex)
        {
            return normals[triangleIndex + 1][normalIndex];
        }


        public Normal InterpolateTriangleNormal(int triIndex, float b1, float b2)
        {
            if (normals.ContainsKey(triIndex + 1)) {
                var n0 = normals[triIndex + 1][0];
                var n1 = normals[triIndex + 1][1];
                var n2 = normals[triIndex + 1][2];

                float b0 = 1f - b1 - b2;
                return b0*n0 + b1*n1 + b2*n2;
            }
            return normals.First().Value.FirstOrDefault();
        }

        public UV InterpolateTriUV(int trIndex, float b1, float b2) {
            var triIndex = trIndex + 1;
            if (texCoords == null || texCoords.Count == 0)
                return new UV { U = b1, V = b2 };
            var t0 = texCoords[triIndex][0];
            var t1 = texCoords[triIndex][1];
            var t2 = texCoords[triIndex][2];
            float b0 = 1f - b1 - b2;

            return t0*b0 + t1*b1 + t2*b2;
        }
        internal void CalcBound()
        {
            bounds = new AABB();
            foreach (var extTriangle in triangles)
            {
                bounds = bounds.Union(scene.Vertices[extTriangle.v0]).Union(scene.Vertices[extTriangle.v1]).Union(scene.Vertices[extTriangle.v2]);
            }
        }

        #region IMesh Members

        public void Init(GeometryInfo model, SceneGeometryInfo scene) {
            this.scene = scene;
            triangles = new List<Triangle>();
            normals = new Dictionary<int, Normal[]>(300);
            texCoords = new Dictionary<int, UV[]>();

            this.MaterialName = model.MaterialName;
            this.MeshName = model.Name;



            var delta = -1;
            var index = 0;
            bool hasNormals = false;
            Normal[] norms = null;
            int[] ni = null;

            bool hasTexCoords = false;
            UV[] texs = null;
            int[] ti = null;
            if (model.IndexData.Any()) {
                hasNormals = model.NormalIndexData.Any();
                hasTexCoords = model.TextureIndexData != null && model.TextureIndexData.Any();
                if (hasTexCoords) {
                    texs = scene.TexCoords.ToUVArray();
                    if (!texs.Any())
                        hasTexCoords = false;
                    ti = model.TextureIndexData.ToArray();
                }
                if (hasNormals) {
                    norms = scene.Normals.Select(item => new Normal(item)).ToArray();
                    ni = model.NormalIndexData.ToArray();
                }

                for (int i = 0; i < model.IndexData.Count; i += 3) {
                    index++;
                    var newTriangle = new Triangle(model.IndexData[i] - delta, model.IndexData[i + 1] - delta, model.IndexData[i + 2] - delta);
                    newTriangle.Index = triangles.Count;
                    newTriangle.Owner = this;
                    triangles.Add(newTriangle);
                    //meshIndexes.Add(extMesh);
                    if (hasTexCoords) {
                        var triIndex = index;
                        var t1 = texs[(ti[i] - 1)];
                        var t2 = texs[(ti[i + 1] - 1)];
                        var t3 = texs[(ti[i + 2] - 1)];
                        texCoords.Add(triIndex, new[] { t1, t2, t3 });
                    }
                    if (hasNormals) {
                        //Add vertice normal
                        var triIndex = index;
                        var norm1 = a*norms[(ni[i] - 1)];
                        var norm2 = a*norms[(ni[i + 1] - 1)];
                        var norm3 = a*norms[(ni[i + 2] - 1)];
                        normals.Add(triIndex, new[] { norm1, norm2, norm3 });
                    }
                    else {
                        var triIndex = index;
                        var norm1 = newTriangle.ComputeVerticeNormal(0, ref scene.Vertices);
                        var norm2 = newTriangle.ComputeVerticeNormal(1, ref scene.Vertices);
                        var norm3 = newTriangle.ComputeVerticeNormal(2, ref scene.Vertices);
                        normals.Add(triIndex, new[] { norm1, norm2, norm3 });
                    }
                }
                //extMesh.Indexes = model.IndexBuffer.Select(item => item - (extMesh.StartVertice - 1)).ToArray();
            }
            else {
                for (int i = 0; i < scene.Vertices.Length; i += 3) {
                    var tri = new Triangle(Array.IndexOf(scene.Vertices, scene.Vertices[i]),
                                           Array.IndexOf(scene.Vertices, scene.Vertices[i + 1]),
                                           Array.IndexOf(scene.Vertices, scene.Vertices[i + 2]));

                    triangles.Add(tri);
                    //meshIndexes.Add(extMesh);
                    index++;
                }
            }
        }

        #endregion
    }
}
